% vim: set ft=erlang : -*- erlang -*- % Magic lines for code editors

% Licensed under the Apache License, Version 2.0 (the "License"); you may not
% use this file except in compliance with the License. You may obtain a copy of
% the License at
%
%   http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
% License for the specific language governing permissions and limitations under
% the License.

%
% Blacklist some bad releases.
%
{ok, Version} = file:read_file(filename:join(
    [code:root_dir(), "releases", erlang:system_info(otp_release), "OTP_VERSION"]
)).

% Version may be binary if file has /n at end :(
% there is no string:trim/1 in Erlang 19 :(
VerString = case Version of
    V when is_binary(V) -> string:strip(binary_to_list(V), right, $\n);
    _ -> string:strip(Version, right, $\n)
end.
VerList = lists:map(fun(X) -> {Int, _} = string:to_integer(X), Int end,
    string:tokens(VerString, ".")).

DisplayMsg = fun(Msg, Args) ->
    Base = iolist_to_binary(io_lib:format(Msg, Args)),
    Lines = binary:split(Base, <<"\n">>, [global]),
    MaxLen = lists:foldl(fun(Line, Acc) ->
        max(Acc, size(Line))
    end, 0, Lines),
    Decoration = iolist_to_binary(["*" || _ <- lists:seq(1, MaxLen)]),
    ReNewLined = [[L, "~n"] || L <- Lines],
    NewLines = ["~n", Decoration, "~n", ReNewLined, Decoration, "~n~n"],
    MsgBin = iolist_to_binary(NewLines),
    io:format(binary_to_list(MsgBin), [])
end.

ErlangTooOld = fun(Ver) ->
    DisplayMsg(
        "This version of Erlang (~p) is too old for use with Apache CouchDB.~n~n"
        "See https://docs.couchdb.org/en/stable/install/unix.html#dependencies~n"
        "for the latest information on dependencies.",
        [Ver]
    ),
    halt(1)
end.

NotSupported = fun(Ver) ->
    DisplayMsg(
        "This version of Erlang (~p) is not officially supported by Apache~n"
        "CouchDB. While we do not officially support this version, there~n"
        "are also no known bugs or incompatibilities.~n~n"
        "See https://docs.couchdb.org/en/stable/install/unix.html#dependencies~n"
        "for the latest information on dependencies.",
        [Ver]
    )
end.

BadErlang = fun(Ver) ->
    DisplayMsg(
        "This version of Erlang (~p) is known to contain bugs that directly~n"
        "affect the correctness of Apache CouchDB.~n~n"
        "You should *NOT* use this version of Erlang.~n~n"
        "See https://docs.couchdb.org/en/stable/install/unix.html#dependencies~n"
        "for the latest information on dependencies.",
        [Ver]
    ),
    case os:getenv("TRAVIS") of
        "true" ->
            io:fwrite("Travis run, ignoring bad release. You have been warned!~n"),
            ok;
        _ ->
            halt(1)
    end
end.

% crypto:pbkdf2_hmac/5 was blocking schedulers in
% versions < 24.3.4.17, < 25.3.2.10 and < 26.2.3
%
case VerList of
    % Leave example around if we have to exlude specific versions
    % [22, 0, N | _] when N < 5 -> BadErlang(VerString);
    [25, 3, 2, N | _] when N < 10 -> BadErlang(VerString);
    [26, 2, N | _] when N < 3 -> BadErlang(VerString);
    _ -> ok
end.

% Set the path to the configuration environment generated
% by `./configure`.

COUCHDB_ROOT = filename:dirname(SCRIPT).
os:putenv("COUCHDB_ROOT", COUCHDB_ROOT).

ConfigureEnv = filename:join(COUCHDB_ROOT, "config.erl").
os:putenv("COUCHDB_CONFIG", ConfigureEnv).

CouchConfig = case filelib:is_file(ConfigureEnv) of
    true ->
        {ok, Result} = file:consult(ConfigureEnv),
        Result;
    false ->
        []
end.

os:putenv("COUCHDB_APPS_CONFIG_DIR", filename:join([COUCHDB_ROOT, "rel/apps"])).

SubDirs = [
    %% must be compiled first as it has a custom behavior
    "src/couch_epi",
    "src/config",
    "src/couch_log",
    "src/b64url",
    "src/exxhash",
    "src/ets_lru",
    "src/couch_quickjs",
    "src/chttpd",
    "src/couch",
    "src/couch_event",
    "src/mem3",
    "src/couch_index",
    "src/couch_mrview",
    "src/couch_replicator",
    "src/couch_pse_tests",
    "src/couch_stats",
    "src/couch_peruser",
    "src/couch_tests",
    "src/couch_dist",
    "src/custodian",
    "src/ddoc_cache",
    "src/dreyfus",
    "src/nouveau",
    "src/fabric",
    "src/global_changes",
    "src/ioq",
    "src/jwtf",
    "src/ken",
    "src/mango",
    "src/rexi",
    "src/setup",
    "src/smoosh",
    "src/weatherreport",
    "src/couch_prometheus",
    "src/couch_scanner",
    "rel"
].

DepDescs = [
%% Independent Apps
{snappy,           "snappy",           {tag, "CouchDB-1.0.9"}},

%% %% Non-Erlang deps
{fauxton,          {url, "https://github.com/apache/couchdb-fauxton"},
                   {tag, "v1.3.1"}, [raw]},
{ibrowse,          "ibrowse",          {tag, "CouchDB-4.4.2-6"}},
{jiffy,            "jiffy",            {tag, "1.1.2"}},
{mochiweb,         "mochiweb",         {tag, "v3.2.2"}},
{meck,             "meck",             {tag, "CouchDB-0.9.2-1"}},
{recon,            "recon",            {tag, "2.5.5"}}
].

WithProper = lists:keyfind(with_proper, 1, CouchConfig) == {with_proper, true}.

OptionalDeps = case WithProper of
    true ->
        [{proper, {url, "https://github.com/proper-testing/proper"}, "a5ae5669f01143b0828fc21667d4f5e344aa760b"}];
    false ->
        []
end.

BaseUrl = "https://github.com/apache/".

MakeDep = fun
    ({AppName, {url, Url}, Version}) ->
        {AppName, ".*", {git, Url, Version}};
    ({AppName, {url, Url}, Version, Options}) ->
        {AppName, ".*", {git, Url, Version}, Options};
    ({AppName, RepoName, Version}) ->
        Url = BaseUrl ++ "couchdb-" ++ RepoName ++ ".git",
        {AppName, ".*", {git, Url, Version}};
    ({AppName, RepoName, Version, Options}) ->
        Url = BaseUrl ++ "couchdb-" ++ RepoName ++ ".git",
        {AppName, ".*", {git, Url, Version}, Options}
end.

AddConfig = [
    {cover_enabled, true},
    {cover_print_enabled, true},
    {require_otp_vsn, "25|26|27"},
    {deps_dir, "src"},
    {deps, lists:map(MakeDep, DepDescs ++ OptionalDeps)},
    {sub_dirs, SubDirs},
    {lib_dirs, ["src"]},
    {erl_opts, [{i, "../"}, {d, 'COUCHDB_ERLANG_VERSION', VerString}]},
    {eunit_opts, [verbose, {report,{eunit_surefire,[{dir,"."}]}}]},
    {xref_checks, [undefined_function_calls, undefined_functions]},
    {plugins, [eunit_plugin]},
    {dialyzer, [
        {plt_location, local},
        {plt_location, COUCHDB_ROOT},
        {plt_extra_apps, [
            asn1, compiler, crypto, inets, kernel, runtime_tools,
            sasl, setup, ssl, stdlib, syntax_tools, xmerl]},
        {warnings, [unmatched_returns, error_handling, race_conditions]}]},
    {post_hooks, [{compile, "escript support/build_js.escript"}]}
].

lists:foldl(fun({K, V}, CfgAcc) ->
    case lists:keyfind(K, 1, CfgAcc) of
        {K, Existent} when is_list(Existent) andalso is_list(V) ->
            lists:keystore(K, 1, CfgAcc, {K, Existent ++ V});
        false ->
            lists:keystore(K, 1, CfgAcc, {K, V})
    end
end, CONFIG, AddConfig).
